package edu.cmu.cs.erl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

//import edu.cmu.cs.erl.rules.PRED_check_3;
import edu.cmu.cs.erl.rules.PRED_check_4;

import jp.ac.kobe_u.cs.prolog.lang.*;

public class ERL {
	static public List<ERLError> runPredicate(Oracle oracle, Term term) throws ERLException {
		return runPredicate(oracle, term, false);
	}
	
	static public List<ERLError> runPredicate(Oracle oracle, Term term, boolean verbose) throws ERLException {
		PrologControl control = new PrologControl();
		Predicate pred = new PRED_check_4();
		
		if (verbose)
			System.out.println("Running " + term.toString());
		
		VariableTerm errorList = new VariableTerm();
		
		Hashtable<JavaObjectTerm, JavaObjectTerm> context = toPLCafeContext(oracle.getStartingContext()); 
		
		Term[] args = new Term[]{new JavaObjectTerm(context), new JavaObjectTerm(oracle), term, errorList};

		if (control.execute(pred, args)) {
			List<ERLError> list = parseErrors(errorList);
			
			if (verbose)
				printErrors(list);
			
			return list;
		}
		else
			throw new ERLException("Failure of Prolog predicate " + pred.toString());
	}
	
	private static Hashtable<JavaObjectTerm, JavaObjectTerm> toPLCafeContext(Hashtable<String, Object> context) {
		Hashtable<JavaObjectTerm, JavaObjectTerm> plContext = new Hashtable<JavaObjectTerm, JavaObjectTerm>();
		
		for (Map.Entry<String, Object> entry : context.entrySet()) {
			plContext.put(new JavaObjectTerm(entry.getKey()), new JavaObjectTerm(entry.getValue()));
		}
		
		return plContext;
	}
	
	private static Hashtable<String, Object> toOracleContext(Hashtable<Term, Term> prologContext) {
		Hashtable<String, Object> context = new Hashtable<String, Object>();
		Map.Entry entry;
		Term term;
		
		for (Object obj : prologContext.entrySet()) {
			entry = (Map.Entry<Term, Term>)obj;
			
			String key = (String)((JavaObjectTerm)entry.getKey()).toJava();
			Object value;
			
			if (entry.getValue().equals(Prolog.Nil)) {
				value = null;
			}
			else
				value = ((JavaObjectTerm)entry.getValue()).toJava();
			
			if (key != null && value != null)
				context.put(key, value);
		}
		return context;
	}

	static public boolean evaluate(Object oracle, Hashtable context, Object atomic) {
try {
	Hashtable<String, Object> oracleContext = toOracleContext(context);
		if (atomic instanceof VariableTerm) {
			Object javaObject = ((VariableTerm)atomic).toJava();
			return ((Oracle)oracle).evaluate(oracleContext, javaObject);
		}
		else
			return ((Oracle)oracle).evaluate(oracleContext, atomic);
	}
	catch (Throwable err) {
		err.printStackTrace();
		return false;
	}
}
	
	static public String getMessage(Oracle oracle, Hashtable context, Object atomic) {
		try {
			return oracle.getMessage(toOracleContext(context), atomic);
		}
		catch (Throwable err) {
			err.printStackTrace();
			return "";
		}
	}
	
	static public String getNotMessage(Oracle oracle, Hashtable context, Object atomic) {
		try {
			return oracle.getNotMessage(toOracleContext(context), atomic);
		}
		catch (Throwable err) {
			err.printStackTrace();
			return "";
		}
	}
	
	static public String getIsMessage(Oracle oracle, Hashtable context, Object atomic) {
		try {
			return oracle.getIsMessage(toOracleContext(context), atomic);
		}
		catch (Throwable err) {
			err.printStackTrace();
			return "";
		}
	}
	
	static public String getIsNotMessage(Oracle oracle, Hashtable context, Object atomic) {
		try {
			return oracle.getIsNotMessage(toOracleContext(context), atomic);
		}
		catch (Throwable err) {
			err.printStackTrace();
			return "";
		}
	}
	
	
	static public Term getList(Object oracle, Hashtable context, Object groundTerm) {		
		try {
			List<Object> list = ((Oracle)oracle).getObjects(toOracleContext(context), groundTerm);
	
			return TermBuilder.makeList(list);
		}
		catch (Throwable err) {
			err.printStackTrace();
			return TermBuilder.makeList(new ArrayList());
		}
	}

	private static void printErrors(List<ERLError> list) {
		if (list.isEmpty())
			System.out.println("Found no errors");
		else {
			System.out.println("Found " + list.size() + "errors:");
			for (ERLError error : list) {
				System.out.println(error.getErrorMessage());
				System.out.println("Responsible: " + error.getOwner());
			}
		}
		
	}

	private static List<ERLError> parseErrors(VariableTerm errorVar) throws ERLException {
		Term errors = errorVar.dereference(); 
		Object responsibleObject;
		String message;
		ListTerm errorList;
		ArrayList<ERLError> list = new ArrayList<ERLError>();
		Term[] pair;
		ERLError err;
		
		if (errors.dereference().isNil())
			return list;
		if (errors.dereference().isList()) {			
			do {
				errorList  = (ListTerm)errors;
				pair = ((StructureTerm)errorList.car().dereference()).args();
				assert (pair.length == 2);
				
				if (pair[0].dereference().isSymbol() && ((SymbolTerm)pair[0].dereference()).name().equals("null"))
					responsibleObject = null;
				else
					responsibleObject = pair[0].toJava();				
				message = (String)pair[1].toJava();
				
				message = prettifyMessage(message);
				
				err = new ERLError(responsibleObject, message);
				list.add(err);
				errors = errorList.cdr().dereference();
			}
			while (!errors.dereference().isNil());
			
			return list;
		}
		
		throw new ERLException("Return from check was not a list");
	}

	private static String prettifyMessage(String message) {
		if (message.equals(""))
			return message;
		
		String first = Character.toString(message.charAt(0));
		String pretty = message.replaceFirst(first, first.toUpperCase());
		
		return pretty + ".";
	}
}
